<template>
  <div class="root">
    <div class="header-wrapper">
      <div class="header">
        <div style="display: flex; align-items: center">
          <SvgIcon
            name="icon-back"
            v-if="fileTree.length"
            style="cursor: pointer; margin-right: 8px"
            @click="handleBackClick"
          />
          拓展应用工程项目资料
        </div>
        <div class="header-controls">
          <el-autocomplete
            v-if="fileTree.length"
            v-model="searchText"
            placeholder="搜索"
            clearable
            value-key="name"
            :fetch-suggestions="querySearch"
            style="width: 300px; margin-right: 16px"
            size="large"
            @select="handleSelect"
          >
            <template #prefix>
              <el-icon style="vertical-align: middle; color: #000">
                <Search />
              </el-icon>
            </template>
          </el-autocomplete>
          <el-select
            v-if="fileTree.length"
            v-model="columns"
            placeholder="显示列数"
            size="large"
            style="width: 80px"
          >
            <el-option
              v-for="col in columnOptions"
              :key="col.value"
              :label="col.label"
              :value="col.value"
            />
          </el-select>
        </div>
      </div>
    </div>
    <div class="main">
      <template v-if="!fileTree.length">
        <div style="display: flex; justify-content: space-between; align-items: center">
          <h1>选择本地目录</h1>
          <el-dropdown @command="handleCommand" trigger="click">
            <span class="el-dropdown-link" style="cursor: pointer">
              {{ directoryMode === 'select' ? '目录上传模式' : '路径输入模式' }}
              <el-icon class="el-icon--right">
                <arrow-down />
              </el-icon>
            </span>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item command="select">目录上传模式</el-dropdown-item>
                <el-dropdown-item command="input" divided>路径输入模式</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </div>
        <el-divider style="margin: 16px 0"></el-divider>
        <div class="content-wrapper">
          <!-- 左侧文件选择区域 -->
          <div class="file-select-area">
            <div style="display: flex; align-items: center" v-if="directoryMode === 'input'">
              <el-autocomplete
                v-model="pathValue"
                :fetch-suggestions="dirQuerySearch"
                placeholder="请输入目录路径"
                value-key="path"
                size="large"
                clearable
                @keyup.enter="handleSubmit"
                @select="dirHandleSelect"
              >
                <template #prefix>
                  <el-icon style="vertical-align: middle; color: #000">
                    <Search />
                  </el-icon>
                </template>
              </el-autocomplete>
              <el-button type="primary" @click="handleSubmit" style="margin-left: 8px"
                >提交</el-button
              >
            </div>
            <div
              class="upload-zone"
              @click="triggerFileSelect"
              v-else-if="directoryMode === 'select'"
            >
              <div class="upload-icon">
                <el-icon style="font-size: 78px; color: #3498db"><UploadFilled /></el-icon>
              </div>
              <p>点击选择文件夹</p>
              <input
                type="file"
                ref="fileInput"
                style="display: none"
                directory
                webkitdirectory
                multiple
                @change="handleFilesSelected"
              />
            </div>
            <el-checkbox v-model="notShowHiddenFiles" style="margin-top: 12px"
              >不显示隐藏文件/目录（.开头的文件/目录）</el-checkbox
            >
          </div>

          <!-- 右侧提示区域 -->
          <div class="tips-area">
            <h3>重要提示：</h3>
            <p>请按照所示目录结构整理，确保在专业工作目录并包含必要的文件上传。</p>
            <h4>目录结构示例：</h4>
            <div class="directory-structure">
              <div class="dir">项目</div>
              <div class="subdir">├─ 目录1/</div>
              <div class="file">│ ├─ xxxx1.docx</div>
              <div class="file">│ ├─ xxxx1.pdf</div>
              <div class="file">│ └─ 子目录/</div>
              <div class="subdir">├─ 目录2/</div>
              <div class="file">│ ├─ xxxx2.docx</div>
              <div class="file">│ ├─ xxxx2.xlsx</div>
              <div class="file">│ └─ xxxx2.pdf</div>
              <div class="subdir">└─ 目录3/</div>
              <div class="file">└─ xxxx3.pdf</div>
            </div>
          </div>
        </div>
      </template>

      <template v-else>
        <!-- 渲染目录树 -->
        <div class="tree-wrapper">
          <div v-for="tree in fileTree" :key="tree.label" class="tree-item">
            <h3>{{ tree.label }}</h3>
            <el-tree
              class="treeCss"
              highlight-current
              style="max-width: 600px"
              :data="tree.children"
              :props="defaultProps"
              :load="loadNode"
              lazy
              node-key="path"
              :default-expanded-keys="defaultExpandedKeys"
              @node-click="handleNodeClick"
            >
              <template #default="{ node }">
                <!-- 检查配置文件是否包含该文件 -->
                <el-tooltip
                  v-if="isFileConfigured(node.label)"
                  effect="dark"
                  :content="getFileConfig(node.label)"
                  placement="top-start"
                >
                  <div style="display: flex; align-items: center">
                    <SvgIcon :name="getIconName(node)" style="margin-right: 4px" />
                    <span
                      class="font-weight-bold"
                      :title="node.label"
                      v-html="highlightText(node.label)"
                    ></span>
                  </div>
                </el-tooltip>
                <template v-else>
                  <SvgIcon :name="getIconName(node)" style="margin-right: 4px" />
                  <span
                    class="font-weight-bold"
                    :title="node.label"
                    v-html="highlightText(node.label)"
                  ></span>
                </template>
              </template>
            </el-tree>
          </div>
        </div>
      </template>
    </div>
  </div>
</template>

<script setup>
import request from '@/api/request'
import { ArrowDown, Search, UploadFilled } from '@element-plus/icons-vue'
import { computed, onMounted, ref, watch } from 'vue'

const searchText = ref('') // 搜索框中的文本
const allFileNames = ref([]) // 所有文件名称
const fileTree = ref([]) // 目录树
const defaultProps = {
  children: 'children',
  label: 'label',
  isLeaf: 'file',
}
const pathValue = ref('') // 输入的目录路径
const notShowHiddenFiles = ref(true)
// 用于存储配置文件内容
const configData = ref([])

// 监听fileTree变化，重新计算展开节点
watch(
  () => fileTree.value,
  (newTree) => {
    if (newTree && newTree.length > 0) {
      defaultExpandedKeys.value = calculateExpandedKeys(newTree)
    }
  },
  { deep: true },
)

// 组件挂载时加载配置文件
onMounted(() => {
  loadConfig()
  const fileTreeFromStorage = window.localStorage.getItem('fileTree')
  if (fileTreeFromStorage) {
    fileTree.value = JSON.parse(fileTreeFromStorage)
  }
  const inputHistoryFromStorage = window.localStorage.getItem('inputHistory')
  if (inputHistoryFromStorage) {
    inputHistory.value = JSON.parse(inputHistoryFromStorage)
  }
  const pathValueFromStorage = window.localStorage.getItem('pathValue')
  if (pathValueFromStorage) {
    pathValue.value = pathValueFromStorage
  }
  const directoryModeFromStorage = window.localStorage.getItem('directoryMode')
  if (directoryModeFromStorage) {
    directoryMode.value = directoryModeFromStorage
  }
})

const directoryMode = ref('select')

const handleCommand = (command) => {
  window.localStorage.setItem('directoryMode', command)
  directoryMode.value = command
}

const fileInput = ref(null)

const triggerFileSelect = () => {
  fileInput.value.click()
}

const handleFilesSelected = (e) => {
  const files = e.target.files
  if (files && files.length > 0) {
    // 获取第一个文件的webkitRelativePath
    const relativePath = files[0].webkitRelativePath
    // 提取目录名（webkitRelativePath格式为"目录名/文件名"）
    const directoryName = relativePath.split('/')[0]
    // todo 根据目录名获取目录结构
    request
      .post('/api/file/tree', {
        path: directoryName,
        level: 3,
      })
      .then((res) => {
        pathValue.value = res[0].path.split(directoryName)[0] + directoryName

        // 去掉隐藏文件和目录
        if (notShowHiddenFiles.value) {
          res.forEach((item) => {
            item.children = item.children.filter((child) => !child.label.startsWith('.'))
          })
        }
        fileTree.value = res
        window.localStorage.setItem('fileTree', JSON.stringify(res))
      })
  } else {
    ElMessage.error('未选择任何目录')
  }
}

// 提交目录路径
const handleSubmit = () => {
  if (!pathValue.value) {
    ElMessage.error('请输入目录路径')
    return
  }
  window.localStorage.setItem('pathValue', pathValue.value)
  if (!inputHistory.value.some((item) => item.path === pathValue.value)) {
    inputHistory.value.unshift({ path: pathValue.value })
    // 超过5个，删除最后一个
    if (inputHistory.value.length > 5) {
      inputHistory.value.pop()
    }
    // 存储到localStorage
    window.localStorage.setItem('inputHistory', JSON.stringify(inputHistory.value))
  }

  request
    .post('/api/file/tree', {
      path: pathValue.value,
      level: 3,
    })
    .then((res) => {
      // 去掉隐藏文件和目录
      if (notShowHiddenFiles.value) {
        res.forEach((item) => {
          item.children = item.children.filter((child) => !child.label.startsWith('.'))
        })
      }
      fileTree.value = res
      window.localStorage.setItem('fileTree', JSON.stringify(res))
    })
}

// 默认展开的节点key数组
const defaultExpandedKeys = ref([])

// 计算所有三级以内的节点路径
const calculateExpandedKeys = (treeData, currentLevel = 0) => {
  const keys = []

  if (!treeData || !Array.isArray(treeData)) return keys

  treeData.forEach((node) => {
    const nodePath = node.path

    // 如果是三级以内的目录节点，添加到展开列表
    if (currentLevel < 2 && node.children && node.children.length > 0) {
      keys.push(nodePath)
      // 递归处理子节点
      const childKeys = calculateExpandedKeys(node.children, currentLevel + 1)
      keys.push(...childKeys)
    }
  })

  return keys
}

// 列数相关设置
const columns = ref(2) // 默认显示4列
const columnOptions = [
  { label: '1列', value: 1 },
  { label: '2列', value: 2 },
  { label: '3列', value: 3 },
  { label: '4列', value: 4 },
  { label: '5列', value: 5 },
]

// 计算每个树项的宽度百分比
const treeItemWidth = computed(() => {
  const gapWidth = 12 // 对应CSS中的gap计算值
  return `calc(${100 / columns.value}% - ${((columns.value - 1) * gapWidth) / columns.value}px)`
})

// 高亮文本函数
const highlightText = (text) => {
  if (!searchText.value) {
    return escapeHTML(text)
  }
  // 转义正则特殊字符
  const escapedQuery = searchText.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
  // 创建正则表达式，忽略大小写
  const regex = new RegExp(`(${escapedQuery})`, 'gi')
  // 替换匹配的文本为高亮样式
  return escapeHTML(text).replace(regex, '<span class="highlight">$1</span>')
}

// 读取配置文件函数
const loadConfig = async () => {
  try {
    // 在Vue中，public目录下的文件可以通过根路径直接访问
    const response = await fetch('/config.json')
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    configData.value = await response.json()
    return configData.value
  } catch (error) {
    console.error('加载配置文件失败:', error)
    return []
  }
}

// 检测配置文件中是否包含指定文件
const isFileConfigured = (fileName) => {
  return configData.value.some((item) => item.name === fileName)
}

// 根据文件名获取文件的配置信息
const getFileConfig = (fileName) => {
  const file = configData.value.find((item) => item.name === fileName)
  if (!file) {
    return ''
  }
  return file.description || ''
}
const handleNodeClick = (data) => {
  if (!data.file) {
    return
  }
  const filePath = data.path
  const fileName = data.label || data.name

  // 根据文件类型决定预览方式
  const fileExtension = fileName.split('.').pop().toLowerCase()
  const isOfficeFile = ['docx', 'xlsx'].includes(fileExtension)
  const isPdfFile = fileExtension === 'pdf'
  const isTextFile = ['txt', 'js', 'html', 'css', 'json', 'md', 'xml'].includes(fileExtension)
  const isImageFile = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(fileExtension)

  // 图片和文本类型文件直接预览
  const encodedFilePath = encodeURIComponent(filePath)
  if (isImageFile) {
    request
      .post('/api/file/filestream?filePath=' + encodedFilePath, {}, { responseType: 'blob' })
      .then((res) => {
        console.log(res)
        // 创建Blob URL
        const url = URL.createObjectURL(res)

        const newWindow = window.open()
        const html = newWindow.document.createElement('html')
        const head = newWindow.document.createElement('head')
        const title = newWindow.document.createElement('title')
        title.textContent = fileName
        const style = newWindow.document.createElement('style')
        style.textContent = `
          body { margin:0;background:#222;display:flex;justify-content:center;align-items:center;min-height:100vh; }
          img { max-width:100%;max-height:100%; }
        `
        head.appendChild(title)
        head.appendChild(style)
        const body = newWindow.document.createElement('body')
        const img = newWindow.document.createElement('img')
        img.src = url
        body.appendChild(img)
        html.appendChild(head)
        html.appendChild(body)
        newWindow.document.open()
        newWindow.document.appendChild(html)
        newWindow.document.close()
        // 清理Blob URL，避免内存泄漏
        newWindow.onbeforeunload = () => {
          URL.revokeObjectURL(url)
        }
      })
  } else if (isTextFile) {
    request
      .post('/api/file/filestream?filePath=' + encodedFilePath, {}, { responseType: 'text' })
      .then((res) => {
        const newWindow = window.open()
        // 创建HTML结构
        const html = newWindow.document.createElement('html')

        // 创建head部分
        const head = newWindow.document.createElement('head')
        const title = newWindow.document.createElement('title')
        title.textContent = fileName
        const style = newWindow.document.createElement('style')
        style.textContent = `
          body { font-family: monospace; padding: 20px; white-space: pre-wrap; }
        `
        head.appendChild(title)
        head.appendChild(style)
        const body = newWindow.document.createElement('body')
        const pre = newWindow.document.createElement('pre')
        pre.textContent = escapeHTML(res)
        body.appendChild(pre)
        html.appendChild(head)
        html.appendChild(body)
        newWindow.document.open()
        newWindow.document.appendChild(html)
        newWindow.document.close()
      })
  } else if (isPdfFile) {
    // 去掉fileName后缀.pdf
    const newName = fileName.replace('.pdf', '')

    const pdfUrl =
      location.origin + '/pdf/web/viewer.html?docId=' + encodedFilePath + '&name=' + newName
    window.open(pdfUrl, '_blank')
  } else if (isOfficeFile) {
    const path = fileExtension === 'docx' ? '/#/word' : '/#/excel2'
    window.open(
      `${path}?fileName=` + encodeURIComponent(fileName) + '&fileUrl=' + encodedFilePath,
      '_blank',
    )
  }
}

const loadNode = (node, resolve) => {
  if (node.level === 0) {
    return resolve(node.data)
  } else {
    request
      .post('/api/file/tree', {
        path: node.data.path,
        level: 1,
      })
      .then((res) => {
        // 去掉隐藏文件和目录
        if (notShowHiddenFiles.value) {
          res = res.filter((item) => !item.label.startsWith('.'))
        }
        return resolve(res)
      })
  }
}

const inputHistory = ref([])
const dirQuerySearch = (queryString, callback) => {
  const results = queryString
    ? inputHistory.value.filter((item) =>
        item.path.toLowerCase().includes(queryString.toLowerCase()),
      )
    : inputHistory.value
  callback(results)
}

const dirHandleSelect = (item) => {
  pathValue.value = item.path
}

// HTML转义函数，防止XSS攻击
function escapeHTML(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;')
}

// 根据node节点是否文件及其后缀判断icon
const getIconName = (node) => {
  if (node.data.file) {
    const fileExtension = node.label.split('.').pop().toLowerCase()
    const iOfficeFile = fileExtension === 'docx'
    const isXlsxFile = fileExtension === 'xlsx'
    const isPdfFile = fileExtension === 'pdf'
    const isTextFile = ['txt', 'js', 'html', 'css', 'json', 'md', 'xml'].includes(fileExtension)
    const isImageFile = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(fileExtension)
    switch (true) {
      case iOfficeFile:
        return 'icon-file_type_word'
      case isXlsxFile:
        return 'icon-file_type_excel'
      case isPdfFile:
        return 'icon-file_type_pdf'
      case isTextFile:
        return 'icon-file_type_text'
      case isImageFile:
        return 'icon-file_type_image'
      default:
        return 'icon-default_file'
    }
  } else {
    return 'icon-default_folder'
  }
}

// 搜索建议
const querySearch = (queryString, callback) => {
  if (!queryString) {
    return callback([])
  }
  request
    .post('/api/file/search', {
      search: queryString,
      folder: pathValue.value,
    })
    .then((res) => {
      callback(res)
    })
}

// 处理选择事件
const handleSelect = (node) => {
  node.file = true
  handleNodeClick(node)
}

// 点击返回上一级目录
const handleBackClick = () => {
  searchText.value = ''
  fileTree.value = []
  allFileNames.value = []
  window.localStorage.removeItem('fileTree')
  window.localStorage.removeItem('pathValue')
}
</script>

<style lang="scss" scoped>
.root {
  --tw-ring-offset-shadow: 0 0 #0000;
  --tw-ring-shadow: 0 0 #0000;
  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}

@media (min-width: 640px) {
  .header,
  .main {
    max-width: 640px;
  }
}

@media (min-width: 768px) {
  .header,
  .main {
    max-width: 768px;
  }
}

@media (min-width: 1024px) {
  .header,
  .main {
    max-width: 1024px;
  }
}

@media (min-width: 1280px) {
  .header,
  .main {
    max-width: 1280px;
  }
}

@media (min-width: 1536px) {
  .header,
  .main {
    max-width: 1536px;
  }
}

.header-wrapper {
  position: sticky;
  top: 0;
  background-color: white;
  box-shadow:
    var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
  display: flex;
  align-items: center;
  justify-content: space-between;
  z-index: 1000;
}

.header {
  width: 100%;
  padding: 1rem;
  display: flex;
  justify-content: space-between;
  margin: 0 auto;
  color: rgb(31 41 55);
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1.75rem;
}

.header-controls {
  display: flex;
  align-items: center;
  gap: 16px;
}

.main {
  margin: 0 auto;
  padding: 2rem;
  margin-top: 24px;
  color: #333;
  width: 100%;
  background-color: white;
  box-shadow:
    var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
  h1 {
    font-size: 1.8rem;
    margin-bottom: 2rem;
    color: #333;
  }

  .content-wrapper {
    display: flex;
    gap: 4rem;
    align-items: center;

    @media (max-width: 768px) {
      flex-direction: column;
    }
  }

  .file-select-area {
    flex: 1;

    .upload-zone {
      border: 2px dashed #ddd;
      border-radius: 8px;
      padding: 6rem 1rem;
      text-align: center;
      cursor: pointer;
      transition: all 0.3s ease;

      &:hover {
        border-color: #3498db;
        background-color: #f9f9f9;
      }

      .upload-icon {
        margin-bottom: 1rem;
      }

      p {
        color: #666;
        font-size: 1rem;
      }
    }
  }

  .tips-area {
    flex: 1;
    max-width: 600px;

    h3 {
      color: #e74c3c;
      font-size: 1.2rem;
      margin-bottom: 1rem;
    }

    h4 {
      color: #333;
      font-size: 1rem;
      margin-top: 1.5rem;
      margin-bottom: 0.5rem;
    }

    p {
      color: #666;
      line-height: 1.6;
      margin-bottom: 1rem;
    }

    .directory-structure {
      font-family: 'Courier New', monospace;
      font-size: 0.9rem;
      line-height: 1.5;
      padding: 1rem;
      background-color: #f5f5f5;
      border-radius: 4px;
      white-space: pre-wrap;

      .dir {
        font-weight: bold;
        color: #3498db;
      }

      .subdir {
        color: #2ecc71;
      }

      .file {
        color: #333;
      }
    }
  }

  .tree-wrapper {
    display: flex;
    flex-wrap: wrap;
    // justify-content: space-between;
    gap: 12px;
  }

  .tree-item {
    width: v-bind(treeItemWidth);
    // min-width: 300px;
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 1rem;
    box-sizing: border-box;
    overflow-x: auto;
  }
}

.font-weight-bold {
  font-weight: 500;
  color: #333;
  display: inline-block;
  flex: 1;
  max-width: calc(100% - 18px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

// 高亮样式
:deep(.highlight) {
  background-color: #ffeb3b;
  color: #000;
  border-radius: 2px;
  // padding: 0 2px;
  // font-weight: bold;
}
</style>
